`include "defines.v"
`include "inst_defines.v"
`include "aluop_defines.v"

module ex(
    input wire clock,
    input wire rst,

    input  wire    [`I_BUS]         pc_i,
    input  wire    [5 : 0]          aluop_i,
    input  wire    [`REG_BUS]       op1_i,
    input  wire    [`REG_BUS]       op2_i,
    input  wire    [`REG_BUS]       imm_i,
    input  wire                     rd_w_ena_i,
    input  wire    [4 : 0]          rd_w_addr_i,
    input  wire    [`REG_BUS]       csr_data_i, 

    output reg                      rd_w_ena_o,
    output reg     [4 : 0]          rd_w_addr_o, 
    output reg     [`REG_BUS]       rd_data_o,
    output reg     [`REG_BUS]       csr_data_o,

    //访存相关
    output reg     [5 : 0]          aluop_o,
    output reg     [`D_BUS]         mem_addr_o,
    output reg     [`REG_BUS]       rs2_o,         //store将第二个操作数存到D-RAM
    output reg     [`I_BUS]         new_pc_o,
    //分支指令控制信号
    output wire                     new_branch,
    output reg                      halt_ena,
    output reg                      skip                  


);
    //分支跳转相关信号
    reg   jump;

    // 部分计算结果
    wire  [`REG_BUS]    add_result;
    wire  [`REG_BUS]    sub_result;
    wire  [`REG_BUS]    sll_result;
    wire  [`REG_BUS]    srl_result;
    wire  [`REG_BUS]    sra_result;
    wire  [31:0    ]    sllw_result;
    wire  [31:0]        srlw_result;
    wire  [31:0]        sraw_result;

    wire  [`REG_BUS]    sra_l;            //sra计算时的左边符号部分
    wire  [`REG_BUS]    sra_r;            //sra计算时的右边移位部分
    wire  [31:0]        sraw_l;           //sraw计算时的左边符号部分
    wire  [31:0]        sraw_r;           //sraw计算时的右边移位部分

    //提前计算出一些下面会用到的结果
    assign add_result  = op1_i + op2_i;
    assign sub_result  = op1_i - op2_i;
    assign sll_result  = op1_i << op2_i[5:0]; //sll,slli
    assign srl_result  = op1_i >> op2_i[5:0]; //srl,srli
    assign sllw_result = op1_i[31:0] << op2_i[4:0]; //sllw,slliw
    assign srlw_result = op1_i[31:0] >> op2_i[4:0]; //srlw,srliw
    //算数右移计算
    assign sra_l       = 64'hffff_ffff_ffff_ffff >> op2_i[5:0];
    assign sra_r       = op1_i >> op2_i[5:0];
    assign sra_result  = (sra_l & sra_r) | ({64{op1_i[63]}} & (~sra_l));

    assign sraw_l       = 32'hffff_ffff >> op2_i[4:0];
    assign sraw_r       = op1_i[31:0] >> op2_i[4:0];
    assign sraw_result  = (sraw_l & sraw_r) | ({32{op1_i[31]}} & (~sraw_l));

    //alu
    always @ (*) begin
        rd_w_ena_o    = rd_w_ena_i;
        rd_w_addr_o   = rd_w_addr_i;
        rs2_o         = op2_i;
        aluop_o       = aluop_i;
        if(rst == `RST) begin
            rd_data_o  = `ZERO_WORD;
        end
        else begin
            case (aluop_i)
                `ALUOP_NOP : rd_data_o = `ZERO_WORD;
                `ALUOP_ADD : rd_data_o = add_result; 
                `ALUOP_SUB : rd_data_o = sub_result;
                `ALUOP_ADDW: rd_data_o = {{32{add_result[31]}}, add_result[31:0] }; 
                `ALUOP_SUBW: rd_data_o = {{32{sub_result[31]}}, sub_result[31:0] };                
                `ALUOP_SLT : rd_data_o = $signed(op1_i) < $signed(op2_i); 
                `ALUOP_SLTU: rd_data_o = op1_i < op2_i;
                `ALUOP_AND : rd_data_o = op1_i & op2_i;                 
                `ALUOP_OR  : rd_data_o = op1_i | op2_i;                 
                `ALUOP_XOR : rd_data_o = op1_i ^ op2_i;                   
                `ALUOP_SLL : rd_data_o = sll_result;          
                `ALUOP_SRL : rd_data_o = srl_result;                      
                `ALUOP_SRA : rd_data_o = sra_result;
                `ALUOP_SLLW: rd_data_o = {{32{sllw_result[31]}}, sllw_result[31:0] };          
                `ALUOP_SRLW: rd_data_o = {{32{srlw_result[31]}}, srlw_result[31:0] };                       
                `ALUOP_SRAW: rd_data_o = {{32{sraw_result[31]}}, sraw_result[31:0] }; 

                `ALUOP_LUI : rd_data_o = imm_i; 
                `ALUOP_AUIPC:rd_data_o = pc_i + imm_i; 
   
                `ALUOP_JAL : rd_data_o = pc_i + 4; 
                `ALUOP_JALR: rd_data_o = pc_i + 4; 

                `ALUOP_CSRRW:rd_data_o = csr_data_i; 
                `ALUOP_CSRRS:rd_data_o = csr_data_i; 
                `ALUOP_CSRRC:rd_data_o = csr_data_i; 
                
                default    : rd_data_o = `ZERO_WORD;  
            endcase
        end
    end

    always @ (*) begin
        if(rst == `RST) begin
            mem_addr_o = `ZERO_WORD; 
        end
        else begin
            case (aluop_i)
                `ALUOP_LB  : mem_addr_o = op1_i + imm_i;
                `ALUOP_LH  : mem_addr_o = op1_i + imm_i;
                `ALUOP_LW  : mem_addr_o = op1_i + imm_i;
                `ALUOP_LD  : mem_addr_o = op1_i + imm_i;
                `ALUOP_LBU : mem_addr_o = op1_i + imm_i;
                `ALUOP_LHU : mem_addr_o = op1_i + imm_i;
                `ALUOP_LWU : mem_addr_o = op1_i + imm_i;

                `ALUOP_SB  : mem_addr_o = op1_i + imm_i;
                `ALUOP_SH  : mem_addr_o = op1_i + imm_i;
                `ALUOP_SW  : mem_addr_o = op1_i + imm_i; 
                `ALUOP_SD  : mem_addr_o = op1_i + imm_i;

                default    : mem_addr_o = `ZERO_WORD;
            endcase
        end
    end
    always @ (*) begin
        if(rst == `RST) begin
            new_pc_o   = `ZERO_WORD; 
            jump       = 1'b0;
        end
        else begin
            case (aluop_i)
                `ALUOP_JAL : begin jump = 1'b1; new_pc_o = pc_i + imm_i; end
                `ALUOP_JALR: begin jump = 1'b1; new_pc_o = (op1_i + imm_i) & ~64'b1; end
                `ALUOP_BEQ : begin jump = op1_i == op2_i; new_pc_o = pc_i + imm_i; end
                `ALUOP_BNE : begin jump = op1_i != op2_i; new_pc_o = pc_i + imm_i; end
                `ALUOP_BLT : begin jump = $signed(op1_i) < $signed(op2_i); new_pc_o = pc_i + imm_i; end
                `ALUOP_BLTU: begin jump = op1_i <  op2_i; new_pc_o = pc_i + imm_i; end
                `ALUOP_BGE : begin jump = $signed(op1_i) >= $signed(op2_i); new_pc_o = pc_i + imm_i; end
                `ALUOP_BGEU: begin jump = op1_i >= op2_i; new_pc_o = pc_i + imm_i; end
                
                default    : begin jump = 1'b0; new_pc_o = `ZERO_WORD; end
            endcase
        end
    end
    
    always @ (*) begin
        if(rst == `RST) 
            begin
                halt_ena = 1'b0; 
                skip = 1'b0;                
            end
        else begin
            case(aluop_i)
                `ALUOP_FALT  : begin 
                                    //$display("The program falt!"); 
                                    //$display("The a0 is %h. ", op1_i);
                                    halt_ena = 1'b1; 
                                    skip = 1'b1;
                                end
                `ALUOP_PUTCH : begin
                                    halt_ena = 1'b0; 
                                    skip = 1'b1;
                                    //$write("%c",op1_i);
                                end 
                                
                default :
                    begin
                        halt_ena = 1'b0; 
                        skip = 1'b0;                
                    end
            endcase
        end
    end

    always @ (posedge clock) begin
            case(aluop_i)
                `ALUOP_FALT  : begin 
                                    $display("The program falt!"); 
                                    $display("The a0 is %h. ", op1_i);
                                end
                `ALUOP_PUTCH : begin

                                    $write("%c",op1_i);
                                end                 
                default :;
            endcase
    end
// csr
    always @ (*) begin
        if(rst == `RST) 
            begin
                csr_data_o  = `ZERO_WORD;
            end
        else
            case(aluop_i)
                `ALUOP_CSRRW : csr_data_o = op1_i;
                `ALUOP_CSRRS : csr_data_o = op1_i | csr_data_i;
                `ALUOP_CSRRC : csr_data_o = op1_i & csr_data_i;
                default      : csr_data_o  = `ZERO_WORD;
            endcase
    end
    assign new_branch = ((jump == 1'b1) && (new_pc_o != pc_i + 4));

endmodule